package netmorph.model;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
 


public class NetmorphExec implements Callable<File> {
	
	public static final String DEFAULT_DIRNAME = "NetmorphModelCreator";
	
	File tempDir;
	File output;
	File neurons;
	
	List<String> warnings;
	
	List<String> cmd;

	ConfigFile config;
	
	public NetmorphExec(ConfigFile cfg) {
		warnings = new ArrayList<String>();
		this.config = cfg;
	}
	
	public NetmorphExec(ConfigFile cfg, String dirName) throws IOException {
		this(cfg);
		
		tempDir = new File(System.getProperty("java.io.tmpdir"), dirName);
		if (!tempDir.exists()) tempDir.mkdir();
		if (!tempDir.isDirectory()) {
			throw new IOException("Can't create directory " + tempDir + ", already exists as a file!");
		}
		
		for (File f : tempDir.listFiles()) {
			if (f.getName().startsWith(".")) continue;
			f.delete();
		}
	}

	public void addParam(String key, String value) 
	{
		config.put(key, value);
	}

	/**
	 * Write config file to a single string
	 * @return
	 */
	public String createConfigAsString()
	{
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		PrintWriter pw = new PrintWriter(baos);
		
		pw.write("substitute=APD:all_apical_pyramidal_dendrites;\r\n");
		
		for (String key : config.keySet()) {
			pw.write(key + "=" + config.get(key) +";\r\n");
		}
		output = new File(tempDir, "");
//		pw.write("outattr_directory=" + output.getAbsolutePath()+File.separatorChar+";\r\n");
		pw.flush();
		pw.close();

		return baos.toString();
	}
	
	/**
	 * Write config file to temp dir
	 * @return
	 * @throws IOException
	 */
	private File createConfig() throws IOException
	{
		File cfg = new File(tempDir, "config.txt");
		PrintWriter pw = new PrintWriter(new FileWriter(cfg));
		
		pw.write("substitute=APD:all_apical_pyramidal_dendrites;\r\n");
		
		for (String key : config.keySet()) {
			pw.write(key + "=" + config.get(key) +";\r\n");
		}
		output = new File(tempDir, "");
//		pw.write("outattr_directory=" + output.getAbsolutePath()+File.separatorChar+";\r\n");
		pw.flush();
		pw.close();
		return cfg;
	}

	/**
	 * Build process run command.
	 * @param cfg
	 * @throws IOException
	 */
	private void buildCommand(File cfg) throws IOException
	{
		cmd = new ArrayList<String>();
		
		File exe1 = new File(System.getProperty("user.home") + File.separator + "bin" + File.separator + "netmorph");
		File exe2 = new File("c:\\netmorph\\netmorph.exe");

		if (exe1.exists() && exe1.canExecute()) cmd.add(exe1.getAbsolutePath());
		else if (exe2.exists() && exe2.canExecute()) cmd.add(exe2.getAbsolutePath());
		else throw new IOException("Can't find netmorph: " + exe1.getAbsolutePath() + " or " + exe2.getAbsolutePath());
		
		cmd.add("include=" + cfg.getAbsolutePath());
	}

	/**
	 * Configure model using current parameters
	 * @throws IOException
	 */
	public void configure() throws IOException
	{
		File cfg = createConfig();
		for (int i = 0; i < 100; i++) {
			try {
				try {
					buildCommand(cfg);
					return;
				} catch (Exception e) {
					Logger.getLogger(NetmorphExec.class.getName()).log(Level.SEVERE, null, e);
				}
				Thread.sleep(60000);
			} catch (InterruptedException ex) {
				Logger.getLogger(NetmorphExec.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
	}

	/**
	 * Configure model using given string (See createConfigAsString())
	 * @param config
	 * @throws IOException
	 */
	public void configure(String config) throws IOException
	{
		File cfg = new File(tempDir, "config.txt");
		
		PrintWriter pw = new PrintWriter(new FileWriter(cfg));
		pw.write(config);
		pw.flush();
		pw.close();
		
		output = new File(tempDir, "");

		buildCommand(cfg);
	}

	private static class Runner implements Runnable {
		List<String> cmd;
		List<String> warnings;
		public Runner(List<String> cmd) {
			this.cmd = cmd;
			this.warnings = new ArrayList<String>();
		}
		@Override
		public void run() {
			try {
				ProcessBuilder b = new ProcessBuilder(cmd);
				b.redirectErrorStream(true);
				Process proc = b.start();
				BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
				while(true) {
					String line = br.readLine();
					if (line == null) break;
					if (line.startsWith("Warning: ")) {
						warnings.add(line);
					}
					else if (line.trim().startsWith("[origin:")) {
						// warning origin spec, ignore.
					}
					else {
						// show only non-warnings on stdout
						System.out.println("*** " + line);
					}
				}
				
				// try to close process
				try { proc.waitFor(); } catch (InterruptedException ex) {}
//				proc.destroy();
				proc = null; b = null;

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Start netmorph simulation. Model has to be configured first.
	 * @throws IOException
	 */
	public void start() throws IOException
	{
		ProcessBuilder b = new ProcessBuilder(cmd);
		b.redirectErrorStream(true);
		Process proc = b.start();
		BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
		while(true) {
			String line = br.readLine();
			if (line == null) break;
			if (line.startsWith("Warning: ")) {
				warnings.add(line);
			}
			else if (line.trim().startsWith("[origin:")) {
				// warning origin spec, ignore.
			}
			else {
				// show only non-warnings on stdout
				System.out.println("*** " + line);
			}
		}
		
		System.out.println("READY. output  "+ output);

		// find .neurons
		for (File f : tempDir.listFiles()) {
			String filename = f.getAbsolutePath();
			if (filename.startsWith(output.getAbsolutePath()) && filename.endsWith(".neurons")) {
				neurons = f;
				break;
			}
		}
		
		// try to close process
		try { proc.waitFor(); } catch (InterruptedException ex) {}
		proc.destroy();
		proc = null; b = null;
	}

	/**
	 * Start simulation in background
	 */
	public void startBG()
	{
		Thread t = new Thread(new Runner(cmd));
		t.start();
		try {
			t.join();
		} catch (InterruptedException e) { e.printStackTrace();	}
		
		System.out.println("READY. output  "+ output);

		// find .neurons
		for (File f : tempDir.listFiles()) {
			String filename = f.getAbsolutePath();
			if (filename.startsWith(output.getAbsolutePath()) && filename.endsWith(".neurons")) {
				neurons = f;
				break;
			}
		}		
	}

	/**
	 * Delete simulation files
	 */
	public void clean()
	{
		for (File f : tempDir.listFiles()) {
			if (f.getName().startsWith(".")) continue;
			f.delete();
		}
		tempDir.delete();
	}
	
	@Override
	public File call() throws Exception 
	{
		start();
		System.out.println("MODEL " + tempDir + " DONE");
		return neurons;
	}

	/**
	 * Get .neurons file path from simulation.
	 * @return
	 */
	public File getNeurons() {
		return neurons;
	}
	
	public List<String> getWarnings()
	{
		return warnings;
	}
}
